
package com.eworking.eim.xmpp;

import java.io.File;

import org.jivesoftware.smack.ConnectionConfiguration;
import org.jivesoftware.smack.ConnectionListener;
import org.jivesoftware.smack.PacketListener;
import org.jivesoftware.smack.Roster;
import org.jivesoftware.smack.SASLAuthentication;
import org.jivesoftware.smack.SmackConfiguration;
import org.jivesoftware.smack.XMPPConnection;
import org.jivesoftware.smack.filter.PacketTypeFilter;
import org.jivesoftware.smack.packet.IQ;
import org.jivesoftware.smack.packet.Packet;
import org.jivesoftware.smack.packet.Presence;
import org.jivesoftware.smack.provider.ProviderManager;
import org.jivesoftware.smackx.GroupChatInvitation;
import org.jivesoftware.smackx.ServiceDiscoveryManager;
import org.jivesoftware.smackx.entitycaps.EntityCapsManager;
import org.jivesoftware.smackx.packet.ChatStateExtension;
import org.jivesoftware.smackx.packet.OfflineMessageInfo;
import org.jivesoftware.smackx.packet.OfflineMessageRequest;
import org.jivesoftware.smackx.ping.packet.Ping;
import org.jivesoftware.smackx.provider.DataFormProvider;
import org.jivesoftware.smackx.provider.DelayInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverInfoProvider;
import org.jivesoftware.smackx.provider.DiscoverItemsProvider;
import org.jivesoftware.smackx.provider.MUCAdminProvider;
import org.jivesoftware.smackx.provider.MUCOwnerProvider;
import org.jivesoftware.smackx.provider.MUCUserProvider;
import org.jivesoftware.smackx.provider.VCardProvider;

import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.net.ConnectivityManager;
import android.os.Build;
import android.os.Build.VERSION_CODES;

import com.eworking.eim.EIMConfig;
import com.eworking.eim.exception.EIMException;
import com.eworking.eim.exception.EIMNetworkUnconnectedException;
import com.eworking.eim.util.EIMLog;
import com.eworking.eim.util.NetUtil;

public class XMPPConnectionManager {
    
    /** 日志标记 */
    private static final String _TAG = XMPPConnectionManager.class.getSimpleName();
    
    /** xmpp资源 */
    private static String _RESOURCE = "mobile";
    
    /** 登陆用户JID */
    private final String userJID;
    
    /** 登陆密码 */
    private final String password;
    
    /** XMPP服务器地址 */
    private final String host;
    
    /** XMPP服务器端口 */
    private final int port = 5222;
    
    private final Context context;
    
    /** 是否强制断开连接 */
    private boolean isForcedDisconnect = false;
    
    /** 是否自动重连 */
    private boolean autoReconnect = false;
    
    /** XMPP连接配置 */
    private ConnectionConfiguration connectionConfiguration;
    
    /** XMPP连接管理 */
    private static XMPPConnectionManager instance;
    
    /** Ping */
    private final PingListener pingListener;
    
    private XMPPConnection connection = null;
    
    /** 外部连接监听器（需要外部注册） */
    private ConnectionListener chatConnectionListener;
    
    /** XMPP内部连接监听 */
    private XMPPConnectionListener xmppConnectionListener;
    
    /** 广播接收 */
    private BroadcastReceiver broadcastReceiver = null;
    
    /**
     * @param userJID 登陆用户JID
     * @param password 登陆用户密码
     * @param host 登陆地址
     * @param context
     */
    public XMPPConnectionManager(String userJID, String password, Context context) {
        this.pingListener = new PingListener();
        this.xmppConnectionListener = new XMPPConnectionListener();
        
        // 创建广播接收器
        this.broadcastReceiver = new BroadcastReceiver() {
            
            @Override
            public void onReceive(Context context, Intent intent) {
                String action = intent.getAction();
                if (!(action.equals(ConnectivityManager.CONNECTIVITY_ACTION))) {
                    EIMLog.d(_TAG, "skip no connectivity action");
                    return;
                }
                // 网络连接是否正常
                boolean isConnectivity = intent.getBooleanExtra(ConnectivityManager.EXTRA_NO_CONNECTIVITY, false);
                if (!isConnectivity) {
                    context.unregisterReceiver(broadcastReceiver);
                    // 设置自动连接xmpp服务器
                    setAutoReconnect(true);
                    autoReconnect = true;
                    Thread thread = new Thread(new Runnable() {
                        
                        @Override
                        public void run() {
                            // 重新连接
                            reloginSync();
                        }
                    });
                    thread.start();
                }
            }
        };
        
        this.userJID = userJID;
        this.password = password;
        this.host = EIMConfig.IM_SERVER;
        this.context = context;
        
        // 初始化连接配置
        initConnectionConfig();
        this.connection = new XMPPConnection(connectionConfiguration);
        //EntityCapsManager.getInstanceFor(connection).disableEntityCaps();
        instance = this;
    }
    
    /**
     * 获取xmppConnectionManager实例
     * 
     * @return
     */
    public static XMPPConnectionManager getInstance() {
        return instance;
    }
    
    public static void resetInstance() {
        instance = null;
    }
    
    /**
     * 设置chat连接监听器
     * 
     * @param connectionListener
     */
    public void setChatConnectionListener(ConnectionListener connectionListener) {
        this.chatConnectionListener = connectionListener;
    }
    
    /**
     * 初始化XMPP连接
     * 
     * @throws EIMNetworkUnconnectedException
     */
    private void initConnection() throws EIMNetworkUnconnectedException {
    	if(this.connection == null){
    		this.connection = new XMPPConnection(connectionConfiguration); 
    	}
        if (!(this.connection.isConnected())) {
            EIMLog.e(_TAG, "Connection is not connected as expected");
            throw new EIMNetworkUnconnectedException("Connection is not connected as expected");
        }
        EntityCapsManager.getInstanceFor(this.connection).disableEntityCaps();
        this.connection.addConnectionListener(this.xmppConnectionListener);
        this.connection.addConnectionListener(this.chatConnectionListener);
        initFeatures();
        PacketTypeFilter packetTypeFilter = new PacketTypeFilter(Ping.class);
        this.connection.addPacketListener(this.pingListener, packetTypeFilter);
    }
    
    /**
     * 建立XMPP连接（同步）
     * 
     * @throws EIMNetworkUnconnectedException
     */
    public synchronized void connectSync() {
        try {
        	if (this.connection == null) {
        		initConnection();
        	}
            synchronized (this.connection) {
                if (this.connection.isConnected()) {
                    EIMLog.d(_TAG, "connection is connected, skip reconnect");
                    return;
                }
                // 建立连接
                this.connection.connect();
            }
            return;
        } catch (Exception e) {
            EIMLog.e(_TAG, "建立与XMPP服务器的连接失败" + e.getMessage(),e);
        }
    }
    /**
     * 重新建立XMPP服务器（异步）
     */
    public void reconnectASync() {
        Thread thread = new Thread(new Runnable() {
            
            public void run() {
                try {
                	connectSync();
                } catch (Exception localException) {
                    EIMLog.e(_TAG, "与XMPP服务器连接失败");
                }
            }
        });
        thread.start();
    }
    
    /**
     * 连接XMPP服务器并登陆（同步）
     * 
     * @throws EIMException
     */
    public void loginSync() throws EIMException {
        this.isForcedDisconnect = false;
        if ((this.connection.isConnected()) && (this.connection.isAuthenticated())) {
            return;
        }
        connectSync();
        initConnection();
        login();
    }
    
    /**
     * 重新连接XMPP服务器（异步）
     */
    private synchronized void reloginSync() {
        this.isForcedDisconnect = false;
        if (this.connection != null) {
            synchronized (this.connection) {
                if ((this.connection.isConnected()) && (this.connection.isAuthenticated())) {
                    EIMLog.d(_TAG, "connection is connected,no need to reconnect");
                    return;
                }
            }
        }
        connectSync();
        login();
    }
    
    /**
     * 断开与XMPP服务器连接
     * 
     * @return
     */
    public boolean disconnect() {
        try {
            setAutoReconnect(false);
            if ((this.connection != null) && (this.connection.isConnected())) {
                EIMLog.d(_TAG, "disconnect, disable autoreconnect");
                this.isForcedDisconnect = true;
                this.connection.disconnect();
                this.connection = null;
            }
            return true;
        } catch (Exception e) {
            EIMLog.e(_TAG, "断开与XMPP服务器连接发生异常：" + e.getMessage(),e);
            return false;
        }
    }
    
    /**
     * 获取XMPP连接对象
     * 
     * @return XMPPConnection
     */
    public XMPPConnection getConnection() {
        return this.connection;
    }
    
    /**
     * 验证XMPP登录状态
     * 
     * @return
     */
    public boolean isAuthentificated() {
        if (connection == null)
            return false;
        else
            return connection.isAuthenticated();
    }
    
    /**
     * XMPP服务器是否已连接
     * 
     * @return
     */
    public boolean isConnected() {
        if (connection == null)
            return false;
        else
            return connection.isConnected();
    }
    
    /**
     * 设置自动重连XMPP服务器
     * 
     * @param isAllowedReconnect
     */
    public void setAutoReconnect(boolean isAllowedReconnect) {
        this.autoReconnect = isAllowedReconnect;
        EIMLog.d(_TAG, "设置XMPP自动重连：" + isAllowedReconnect);
        this.connectionConfiguration.setReconnectionAllowed(isAllowedReconnect);
    }
    
    /**
     * 是否自动重连
     * 
     * @return
     */
    public boolean getAutoReconnect() {
        return this.autoReconnect;
    }
    
    private void onDisconnected() {
        EIMLog.d(_TAG, "on disconnected");
        if (this.context == null)
            return;
        if (this.isForcedDisconnect)
            return;
        registerConnectivityReceiver();
    }
    
    /**
     * 注册广播接收器
     */
    public synchronized void registerConnectivityReceiver() {
        try {
            // 网络是否连接正常WIFI or 3G
            if (!(NetUtil.hasDataConnection(EIMConfig.getInstance().getApplicationContext()))) {
                IntentFilter intentFilter = new IntentFilter(ConnectivityManager.CONNECTIVITY_ACTION);
                EIMLog.d(_TAG, "register connectivity receiver.");
                this.context.registerReceiver(this.broadcastReceiver, intentFilter);
                setAutoReconnect(false);
            }
            EIMLog.d(_TAG, "did not register connectivity receiver. current reconnect is:" + getAutoReconnect());
        } catch (Exception localException) {
            EIMLog.e(_TAG, localException.getMessage());
        }
    }
    
    /**
     * 初始化XMPP连接配置
     */
    private void initConnectionConfig() {
        configure(ProviderManager.getInstance());
        if (this.connectionConfiguration == null) {
            // 设置订阅模式，订阅被忽略
            Roster.setDefaultSubscriptionMode(Roster.SubscriptionMode.manual);
            // 设置SASL认证方式
            SASLAuthentication.supportSASLMechanism("PLAIN");
            // debug模式
            XMPPConnection.DEBUG_ENABLED = EIMConfig.debugMode;
            // 设置回复超时时间
            SmackConfiguration.setPacketReplyTimeout(20000);
            this.connectionConfiguration =
                new ConnectionConfiguration(this.host, this.port, EIMConfig.getInstance().getDomain());
            // 登陆后自动加载好友信息
            this.connectionConfiguration.setRosterLoadedAtLogin(false);
            // 登陆后是否发送状态请求
            this.connectionConfiguration.setSendPresence(false);
            // 设置允许自动重连
            this.connectionConfiguration.setReconnectionAllowed(true);
            this.autoReconnect = true;
            // 是否启用流压缩
            this.connectionConfiguration.setCompressionEnabled(true);
            if (Build.VERSION.SDK_INT >= VERSION_CODES.ICE_CREAM_SANDWICH) {
                this.connectionConfiguration.setTruststoreType("AndroidCAStore");
                this.connectionConfiguration.setTruststorePassword(null);
                this.connectionConfiguration.setTruststorePath(null);
            } else {
                this.connectionConfiguration.setTruststoreType("BKS");
                String bks = System.getProperty("javax.net.ssl.trustStore");
                if (bks == null)
                    bks =
                        System.getProperty("java.home") + File.separator + "etc" + File.separator + "security"
                            + File.separator + "cacerts.bks";
                this.connectionConfiguration.setTruststorePath(bks);
            }
        }
    }
    
    /**
     * 登陆
     */
    private synchronized void login() {
        try {
        	 if (this.connection == null)
             	initConnection();
            if (this.connection.isAuthenticated()) {
                EIMLog.d(_TAG, "already login. skip");
                return;
            }
            if (!(this.connection.isConnected())) {
                EIMLog.e(_TAG, "Connection is not connected as expected");
                throw new EIMNetworkUnconnectedException("Connection is not connected as expected");
            }
            EIMLog.d(_TAG, "try to login with barejid" + this.userJID);
            this.connection.login(this.userJID, this.password, getXMPPResource());
            EIMLog.d(_TAG, "login successfully");
        } catch (Exception e) {
        	EIMLog.e(_TAG, "登录xmpp服务器失败!!!",e);
            try {
                this.connection.removeConnectionListener(this.xmppConnectionListener);
                this.connection.removeConnectionListener(this.chatConnectionListener);
                this.connection.disconnect();
            } catch (Exception e2) {
            }
            this.connection = null;
            return;
        }
        Presence presence = new Presence(Presence.Type.available);
        this.connection.sendPacket(presence);
    }
    
    /**
     * Ping XMPP服务器是否正常
     * 
     * @author yangsong
     */
    private class PingListener implements PacketListener {
        
        @Override
        public void processPacket(Packet packet) {
            EIMLog.d(_TAG, "received ping packet from :" + packet.getFrom());
            if (!(packet instanceof Ping)) {
                return;
            }
            Ping receivePing = (Ping) packet;
            // XMPP服务器返回result类型
            if (receivePing.getType() == IQ.Type.GET) {
                Ping sendPing = new Ping();
                sendPing.setType(IQ.Type.RESULT);
                sendPing.setTo(receivePing.getFrom());
                sendPing.setPacketID(receivePing.getPacketID());
                connection.sendPacket(sendPing);
            }
        }
    }
    
    /**
     * XMPP内部连接监听器
     * 
     * @author yangsong
     */
    private class XMPPConnectionListener implements ConnectionListener {
        
        @Override
        public void connectionClosed() {
            EIMLog.e(_TAG, "connectionClosed");
            onDisconnected();
        }
        
        @Override
        public void connectionClosedOnError(Exception exception) {
            if (exception != null && exception.getMessage() != null && exception.getMessage().contains("conflict")) {
                setAutoReconnect(false);
                EIMLog.e(_TAG, "connection closed caused by conflict. set autoreconnect to false");
            }
            onDisconnected();
        }
        
        @Override
        public void reconnectingIn(int i) {
            EIMLog.d(_TAG, (new StringBuilder("reconnectingIn in ")).append(i).toString());
        }
        
        @Override
        public void reconnectionFailed(Exception arg0) {
            onDisconnected();
        }
        
        @Override
        public void reconnectionSuccessful() {
            EIMLog.d(_TAG, "reconnectionSuccessful");
            
            // 连接成功后，发送激活状态
            Presence presence = new Presence(Presence.Type.available);
            connection.sendPacket(presence);
        }
    }
    
    /**
     * 获取XMPP资源
     * 
     * @return _RESOURCE 默认手机端 mobile
     */
    public static String getXMPPResource() {
        return _RESOURCE;
    }
    
    /**
     * 初始化XMPP特征
     */
    private void initFeatures() {
        ServiceDiscoveryManager localServiceDiscoveryManager = ServiceDiscoveryManager.getInstanceFor(this.connection);
        localServiceDiscoveryManager.setIdentityName("EWorking");
        localServiceDiscoveryManager.setIdentityType("phone");
        localServiceDiscoveryManager.addFeature("http://jabber.org/protocol/disco#info");
        localServiceDiscoveryManager.addFeature("urn:xmpp:avatar:metadata");
        localServiceDiscoveryManager.addFeature("urn:xmpp:avatar:metadata+notify");
        localServiceDiscoveryManager.addFeature("urn:xmpp:avatar:data");
        localServiceDiscoveryManager.addFeature("http://jabber.org/protocol/nick");
        localServiceDiscoveryManager.addFeature("http://jabber.org/protocol/nick+notify");
        localServiceDiscoveryManager.addFeature("http://jabber.org/protocol/muc");
        localServiceDiscoveryManager.addFeature("http://jabber.org/protocol/muc#rooms");
        localServiceDiscoveryManager.addFeature("urn:xmpp:ping");
    }
    
    private void configure(ProviderManager paramProviderManager) {
        EIMLog.d(_TAG, "configure");
        paramProviderManager.addIQProvider("query", "http://jabber.org/protocol/disco#items",
            new DiscoverItemsProvider());
        paramProviderManager
            .addIQProvider("query", "http://jabber.org/protocol/disco#info", new DiscoverInfoProvider());
        paramProviderManager.addExtensionProvider("delay", "urn:xmpp:delay", new DelayInfoProvider());
        ChatStateExtension.Provider localProvider = new ChatStateExtension.Provider();
        paramProviderManager.addExtensionProvider("active", "http://jabber.org/protocol/chatstates", localProvider);
        paramProviderManager.addExtensionProvider("composing", "http://jabber.org/protocol/chatstates", localProvider);
        paramProviderManager.addExtensionProvider("paused", "http://jabber.org/protocol/chatstates", localProvider);
        paramProviderManager.addExtensionProvider("inactive", "http://jabber.org/protocol/chatstates", localProvider);
        paramProviderManager.addExtensionProvider("gone", "http://jabber.org/protocol/chatstates", localProvider);
        paramProviderManager.addIQProvider("ping", "urn:xmpp:ping", Ping.class);
        ProviderManager.getInstance().addIQProvider("vCard", "vcard-temp", new VCardProvider());
        paramProviderManager.addExtensionProvider("x", "http://jabber.org/protocol/muc#user", new MUCUserProvider());
        paramProviderManager.addIQProvider("query", "http://jabber.org/protocol/muc#admin", new MUCAdminProvider());
        paramProviderManager.addIQProvider("query", "http://jabber.org/protocol/muc#owner", new MUCOwnerProvider());
        paramProviderManager.addExtensionProvider("x", "jabber:x:conference", new GroupChatInvitation.Provider());
        paramProviderManager.addIQProvider("offline", "http://jabber.org/protocol/offline",
            new OfflineMessageRequest.Provider());
        paramProviderManager.addExtensionProvider("offline", "http://jabber.org/protocol/offline",
            new OfflineMessageInfo.Provider());
        paramProviderManager.addExtensionProvider("x", "jabber:x:data", new DataFormProvider());
    }
    
    /**
     * 检查网络连接和XMPP服务器连接是否正常
     * 
     * @throws EIMExceptions
     */
    public  void checkConnection()
        throws EIMException {
        if (this.getConnection() == null)
            throw new EIMNetworkUnconnectedException("connection is null");
        if ((!(this.getConnection().isConnected()))
            || (!(this.getConnection().isAuthenticated()))) {
            EIMLog.e(_TAG, "network unconnected");
            if (NetUtil.hasDataConnection(context)) {
                EIMLog.d(_TAG, "try to reconnect after check connection failed");
                this.reconnectASync();
            }else{
            	throw new EIMNetworkUnconnectedException("connection is not connected");
            }
        }
    }
}
